#include "mycgi.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>

#define IS_HEX_NUM(c) ( ( (c)>='0' ) && ( (c)<='9' ) )
#define IS_HEX_LOW_DIGIT(c) ( ( (c)>='a' ) && ( (c)<='f' ) )
#define IS_HEX_HIGH_DIGIT(c) ( ( (c)>='A' ) && ( (c)<='F' ) )
#define IS_HEX(c) ( IS_HEX_NUM(c) || IS_HEX_LOW_DIGIT(c) || IS_HEX_HIGH_DIGIT(c) )

char *myStrCaseStr(char *hystack, const char *needle)
{
    int lenHS = strlen(hystack);
    int lenND = strlen(needle);

    while(lenHS>=lenND)
    {
        int i;

        for(i=0; i<lenND; i++)
        {
            if( tolower(hystack[i])!=tolower(needle[i]) ) break;
            if( (i+1)==lenND ) return hystack;
        }

        --lenHS;
        ++hystack;
    }

    return 0;
}

void printHeader(CGI *cgi)
{
    int i;
    int contentType = 0;

    for(i=0; i<cgi->nCookies; i++)
    {
        Cookie *cookie = &cgi->cookies[i];
        char *value = "";
        char *maxAge = "0";
        char *expires = "Thu, 01 Jan 1970 00:00:00 GMT";

        cookie->flushed = 1;

        if(cookie->value)
        {
            value = cookie->value;
            maxAge = cookie->maxAge;
            expires = cookie->expires;
        }

        printf("Set-Cookie: %s=%s; Version=%s;",cookie->name,value,cookie->version);
        if(cookie->path) printf(" Path=%s;",cookie->path);
        if(cookie->domain) printf(" Domain=%s;",cookie->domain);
        if(maxAge) printf(" Max-Age=%s;",maxAge);
        if(expires) printf(" Expires=%s;",expires);
        if(cookie->secure==CGI_SECURE_YES) printf(" Secure;");
        if(cookie->httpOnly==CGI_HTTPONLY_YES) printf(" HttpOnly;");
        printf("\r\n");
    }

    for(i=0; i<cgi->nHeaderVars; i++)
    {
        CGIHeader *cgiHeader = &cgi->headerVars[i];
        if(cgiHeader->value && !strcmp(cgiHeader->name,"content-type"))
        {
            contentType = 1;
        }
    }

    if(!contentType)
    {
        printf("Content-type: text/html\r\n");
    }

    for(i=0; i<cgi->nHeaderVars; i++)
    {
        CGIHeader *cgiHeader = &cgi->headerVars[i];
        if(cgiHeader->value) printf("%s: %s\r\n",cgiHeader->name,cgiHeader->value);
    }


    printf("\r\n");
}

void cgiStartFlush(CGI *cgi)
{
    char buf[1024];

    if(!cgi->flushed)
    {
        cgi->flushed = 1;
        printHeader(cgi);
    }

    if(cgi->cgiout)
    {
        rewind(cgi->cgiout);
        while(!feof(cgi->cgiout))
        {
            if(!fgets(buf,1024,cgi->cgiout)) break;
            fputs(buf,stdout);
        }
    }

    fclose(cgi->cgiout);
    cgi->cgiout = 0;
}

void cgiStopFlush(CGI *cgi)
{
    cgi->cgiout = tmpfile();
}

int cgiRedirect(CGI *cgi, const char *url)
{
    if(cgi->flushed) return 0;

    char *formatStr = "<html>\n<body>\nPage moved to <a href=\"%s\">%s</a>\n</body>\n</html>\n";

    char *content = (char *)malloc(sizeof(char)*(strlen(formatStr)+strlen(url)*2));
    if(!content) return 0;

    sprintf(content,formatStr,url,url);

    printf("Content-type: text/html\r\n");
    printf("Content-length: %d\r\n", strlen(content) );
    printf("Status: 302 Temporal Relocation\r\n");
    printf("Location: %s\r\n", url);
    printf("\r\n");
    puts(content);

    return 1;
}


FILE *cgiGetOut(CGI *cgi)
{
    if(cgi->cgiout) return cgi->cgiout;
    return stdout;
}

int isHex(char *ptr)
{
    if( !IS_HEX(ptr[0]) ) return 0;
    if( !IS_HEX(ptr[1]) ) return 0;

    return 1;
}

int toInt(char hex)
{
    if( IS_HEX_NUM(hex) ) return hex - '0';
    if( IS_HEX_LOW_DIGIT(hex) ) return 10 + hex - 'a';
    if( IS_HEX_HIGH_DIGIT(hex) ) return 10 + hex - 'A';

    return -1;
}

char hexToChar(char *ptr)
{
    return (toInt(ptr[0])<<4) | toInt(ptr[1]);
}

void cgiDecodeString(char *str)
{
    char *rPtr, *wPtr;

    for(rPtr=str,wPtr=str; *rPtr; rPtr++)
    {
        if (*rPtr == '%')
        {
            if(isHex(rPtr+1))
            {
                *wPtr = hexToChar(rPtr+1);
                rPtr+=2;
                wPtr++;
            }
        }
        else
        {
            *wPtr = *rPtr;
            wPtr++;
        }
    }

    *wPtr = 0;
}

char *cgiEnv(char *envVarName)
{
    return getenv(envVarName);
}

char *getVarPtr(char *ptr, int *nameSz, int *valSz)
{
    char *vBegin, *vEnd, *vValBegin;

    vBegin = vEnd = ptr;
    vValBegin = 0;

    while(1)
    {
        if( (*vEnd=='=') && (vValBegin==0) ) vValBegin = vEnd;
        if( (*vEnd=='&') || (*vEnd==';') || (*vEnd==0) )
        {
            if( vValBegin && ((vValBegin-vBegin)>0) )
            {
                if(nameSz) *nameSz = vValBegin - vBegin;
                if(valSz) *valSz = (vEnd - vValBegin) - 1;
                return vBegin;
            }

            if(*vEnd==0) break;

            vBegin = vEnd+1;
            vValBegin = 0;
        }
        ++vEnd;
    }

    return 0;
}

char *findVarPtr(char *varName, char *ptr, int *valSz)
{
    int vSz;
    int nSz;

    while(ptr = getVarPtr(ptr,&nSz,&vSz))
    {
        char bkp = ptr[nSz];
        ptr[nSz] = 0;

        int equal = !strcmp(varName,ptr);
        ptr[nSz] = bkp;

        if(equal)
        {
            if(valSz) *valSz = vSz;
            return ptr;
        }

        if(!ptr[nSz + 1 + vSz]) break;

        ptr += nSz + 1 +vSz + 1;
    }

    return 0;
}

int fillVar(CGIVars *cgiVars, char *vStart, int nameSz, int valSz)
{
    int i;
    int nVals=1;

    char *name = vStart;
    name[nameSz] = 0;

    for(i=0; i<cgiVars->nvars; i++)
    {
        if(!cgiVars->vars[i].name) break;

        if( !strcmp(cgiVars->vars[i].name, name) )
        {
//variavel jah adicionada -> retorna 1. 
//OBS.: todas as variaveis com mesmo nome sao adicionadas
//      de uma unica vez no codigo mais abaixo.
            vStart[nameSz+1+valSz] = 0; //delimita final do valor.
            return 1;
        }
    }

    char *end = name + nameSz + 1 + valSz;
    while(*end)
    {
        int vSz;
        char *next = findVarPtr(name, end+1, &vSz);
        if(!next) break;
        end = next + nameSz + 1 + vSz;
        ++nVals;
    }

    cgiVars->vars[i].values = (char **)malloc(sizeof(char *)*nVals);
    if(!cgiVars->vars[i].values) return 0;

    cgiVars->vars[i].nvalues = nVals;
    cgiVars->vars[i].name = name;

    int v = 0;
    int vSz = valSz;
    do
    {
        char *val = name + nameSz + 1;

        cgiVars->vars[i].values[v] = val;
        ++v;

        char *end = val + vSz;
        if(*end==0) break;

        name = findVarPtr(name, end+1, &vSz);
    } while(name);

    return 1;
}

CGIVars *parseVars(char *strVars)
{
    int i, j, numVars=0, nameSz, valSz;
    char *vStart;

    for(i=0; strVars[i]; i++)
        if(strVars[i]=='+') strVars[i]=' ';

    vStart = strVars;
    while(vStart = getVarPtr(vStart, &nameSz, &valSz))
    {
        char bkp = vStart[nameSz];
        vStart[nameSz]=0;

        char *found = findVarPtr(vStart, strVars, 0);
        vStart[nameSz] = bkp;

        if( (!found) || (found>=vStart) ) ++numVars;

        if(!vStart[nameSz+valSz+1]) break;

        vStart += nameSz + valSz + 2;
    }

    CGIVars *cgiVars = (CGIVars *)malloc(sizeof(CGIVars));
    if(!cgiVars) return 0;

    cgiVars->vars = (CGIVar *)malloc(sizeof(CGIVar)*numVars);
    if(!cgiVars->vars) goto ERR;

    memset(cgiVars->vars, 0, sizeof(CGIVar)*numVars);
    cgiVars->varsBuf = strVars;
    cgiVars->nvars = numVars;

    vStart = strVars;
    while(vStart = getVarPtr(vStart, &nameSz, &valSz))
    {
        if(!fillVar(cgiVars, vStart, nameSz, valSz)) goto ERR;

        if(!vStart[nameSz+1+valSz]) break;
 
        vStart += nameSz + 1 + valSz + 1;
    }

    for(i=0; i<cgiVars->nvars; i++)
    {
        cgiDecodeString(cgiVars->vars[i].name);
        for(j=0; j<cgiVars->vars[i].nvalues; j++)
            cgiDecodeString(cgiVars->vars[i].values[j]);
    }

    return cgiVars;

ERR:
    if(cgiVars->vars)
    {
        for(i=0; i<cgiVars->nvars; i++)
        {
            //desaloca o array de apontadores dos valores.
            if(cgiVars->vars[i].values)
                free(cgiVars->vars[i].values);
        }
        free(cgiVars->vars);
    }
    free(cgiVars);
}

int loadPOSTVars(CGI *cgi)
{
    char *vars = 0;
    if(cgi->postVars) return 1;

    char *ctype = cgiContentType();
    if(1)//ctype && myStrCaseStr(ctype,"content-type: multipart/form-data"))
    {
        int result = 0;
        char *boundary = "           AaB03x";//myStrCaseStr(ctype, "; boundary=");

        if(boundary)
        {
            int i;

            boundary += 11;
            for(i=0; boundary[i]; i++)
            {
                if( (boundary[i]=='\r') || (boundary[i]=='\n') )
                {
                    boundary[i] = 0;
                    break;
                }
            }

            result = loadMultipart(cgi, boundary, &vars, 0);
        }

        if(!result) goto ERR;

        if(!vars) vars = strdup("");
    }
    else
    {
        char *len = cgiContentLength();

        int length = 0;
        if(len) length = atoi(len);

        if( length<=0 )
        {
            vars = strdup("");
        }
        else
        {
            vars = (char *)malloc(sizeof(char)*(length+2));
            if(vars)
            {
                if(!fgets(vars, length+1, stdin)) goto ERR;
            }
        }

    }

    if(!vars) goto ERR;

    CGIVars *cgiVars = parseVars(vars);
    if(!cgiVars) goto ERR;

    cgi->postVars = cgiVars;

    return 1;

ERR:
    if(vars) free(vars);
    return 0;
}

int loadGETVars(CGI *cgi)
{
    if(cgi->getVars) return 1;

    char *query = cgiQueryString();

    int length = 0;
    if(query) length = strlen(query);

    char *vars;
    if(length<=0)
    {
        vars = strdup("");
    }
    else
    {
        vars = (char *)malloc(sizeof(char)*(length+2));
        if(vars) sprintf(vars, "%s", query);
    }

    if(!vars) return 0;

    CGIVars *cgiVars = parseVars(vars);
    if(!cgiVars) goto ERR;

    cgi->getVars = cgiVars;
    return 1;

ERR:
    free(vars);
    return 0;
}

int cgiLoadVars(CGI *cgi)
{
    int result1 = loadGETVars(cgi);
    int result2 = loadPOSTVars(cgi);

    return result1 && result2;
}

CGIVar *cgiFindVar(CGIVars *cgiVars, char *varName)
{
    int i;

    for(i=0; i<cgiVars->nvars; i++)
    {
        if(!strcmp(cgiVars->vars[i].name,varName))
            return &cgiVars->vars[i];
    }

    return 0;
}

CGIVar *cgiPOTSVar(CGI *cgi, char *varName)
{
    if(!cgi->postVars)
    {
        if(!loadPOSTVars(cgi)) return 0;
    }

    return cgiFindVar(cgi->postVars, varName);
}

CGIVar *cgiGETVar(CGI *cgi, char *varName)
{
    if(!cgi->getVars)
    {
        if(!loadGETVars(cgi)) return 0;
    }

    return cgiFindVar(cgi->getVars, varName);
}

char *normalizedHeaderName(char *name)
{
    while( *name && isspace(*name) ) ++name;

    int i;
    for(i=0; name[i]; i++)
    {
        if( (name[i]==' ') || (name[i]=='\r') || (name[i]=='\n') || (name[i]==':') ) break;
    }

    char *result = strndup(name,i);
    if(!result) return 0;

    result[i] = 0;
    return result;
}

char *normalizedHeaderVal(char *val)
{
    while( *val && isspace(*val) ) ++val;

    int i;
    for(i=0; val[i]; i++)
    {
        if( (val[i]=='\r') || (val[i]=='\n') ) break;
    }

    char *result = strndup(val,i);
    if(!result) return 0;

    result[i] = 0;
    return result;
}

CGIHeader *cgiHeader(CGI *cgi, char *name)
{
    int i;

    for(i=0; i<cgi->nHeaderVars; i++)
    {
        if(!strcmp(cgi->headerVars[i].name,name))
            return &cgi->headerVars[i];
    }

    return 0;
}

int cgiClearHeader(CGI *cgi, char *name)
{
    if(cgi->flushed) return 0;

    int i;
    for(i=0; i<cgi->nHeaderVars; i++)
    {
        CGIHeader *header = &cgi->headerVars[i];

        if(!header->value) continue;

        if(strcmp(header->name,name))
        {
            free(header->value);
            header->value = 0;
            return 1;
        }
    }

    return 1;
}

CGIHeader *cgiSetHeader(CGI *cgi, char *name, char *value)
{
    int i;

    if(cgi->flushed) return 0;

    for(i=0; i<cgi->nHeaderVars; i++)
    {
        CGIHeader *header = &cgi->headerVars[i];

        if(!strcmp(header->name,name))
        {

            char *newVal = 0;

            if(value)
            {
                newVal = normalizedHeaderVal(value);
                if(!newVal) return 0;
            }

            if(header->value)
                free(header->value);

            header->value = newVal;
            return header;
        }
    }

    if(cgi->nHeaderVars == cgi->maxHeaderVars)
    {
        CGIHeader *newHeaderVars = (CGIHeader *)realloc(cgi->headerVars,sizeof(CGIHeader)*(cgi->maxHeaderVars+16));
        if(!newHeaderVars) return 0;

        cgi->maxHeaderVars += 16;
        cgi->headerVars = newHeaderVars;
    }


    char *nm = normalizedHeaderName(name);
    if(!nm) return 0;

    cgi->headerVars[cgi->nHeaderVars].name = nm;

    char *val = 0;
    if(value)
    {
        val = normalizedHeaderVal(value);
        if(!val)
        {
            free(nm);
            return 0;
        }
    }
    cgi->headerVars[cgi->nHeaderVars].value = val;

    ++cgi->nHeaderVars;

    return &cgi->headerVars[cgi->nHeaderVars-1];
}

char *normalizedCookieParamName(char *name)
{
    while(*name && isspace(*name)) ++name;

    int i;
    for(i=0; name[i]; i++)
    {
        if( isspace(name[i]) || (name[i]==';') || (name[i]==',') || (name[i]=='=') ) break;
    }

    char *result = strndup(name,i);
    if(!result) return 0;

    result[i] = 0;
    return result;
}

char *normalizedCookieParamValue(char *value)
{
    while(*value && isspace(*value)) ++value;

    int i;
    if(*value=='"')
    {
        ++value;
        for(i=0; value[i] && (value[i]!='"') ; i++);
    }
    else
    {
        for(i=0; value[i]; i++)
        {
            if( isspace(value[i]) || (value[i]==';') || (value[i]==',') ) break;
        }
    }

    char *result = strndup(value,i);
    if(!result) return 0;

    result[i] = 0;
    return result;
}

Cookie *setCookie(CGI *cgi, char *name, char *version, char *value, char *domain, char *path, char *comment, int secure, int httpOnly, char *maxAge, char *expires)
{
    if(version==0) version = "0";

    int i;
    for(i=0; i<cgi->nCookies; i++)
    {
        if(!strcmp(cgi->cookies[i].version,version) && !strcmp(cgi->cookies[i].name,name))
        {
            int result = cgiSetCookieParams(&cgi->cookies[i],0,value,domain,path,comment,secure,httpOnly,maxAge,expires);
            if(result) return &cgi->cookies[i];
            return 0;
        }
    }

    if(cgi->nCookies == cgi->maxCookies)
    {
        Cookie *newCookies = (Cookie *)realloc(cgi->cookies, sizeof(Cookie)*(cgi->maxCookies+16));
        if(!newCookies) return 0;

        cgi->cookies = newCookies;
    }

    char *newName = normalizedCookieParamName(name);
    if(!newName) return 0;

    memset(&cgi->cookies[cgi->nCookies], 0, sizeof(Cookie));

    int result = cgiSetCookieParams(&cgi->cookies[cgi->nCookies],version,value,domain,path,comment,secure,httpOnly,maxAge,expires);
    if(result)
    {
        cgi->cookies[cgi->nCookies].name = newName;
        ++cgi->nCookies;
        return &cgi->cookies[cgi->nCookies-1];
    }

    free(newName);
    return 0;
}

int putCookie(CGI *cgi, Cookie *current, Cookie *defaultCookie)
{
    char *version = current->version;
    if( !version && defaultCookie->version ) version = defaultCookie->version;

    char *domain = current->domain;
    if( !domain && defaultCookie->domain ) domain = defaultCookie->domain;

    char *path = current->path;
    if( !path && defaultCookie->path ) path = defaultCookie->path;

    setCookie(cgi, current->name, version, current->value, domain, path, 0, 0, 0, 0, 0);

    free(current->name);
    free(current->value);
    if(current->version) free(current->version);
    if(current->domain) free(current->domain);
    if(current->path) free(current->path);

    current->name = current->value = current->version = current->domain = current->path = 0;
}

int cgiLoadCookies(CGI *cgi)
{
    if(cgi->cookies) return 1;

    char *cookies = cgiHttpCookie();
    if(!cookies) return 1;

    char isDefault = 1;
    Cookie defaultCookie;
    Cookie current;

    memset(&defaultCookie, 0, sizeof(Cookie));
    memset(&current, 0, sizeof(Cookie));

    char *name = cookies;
    char *value = 0;
    char *end = name;

    while(1)
    {
        if(*end=='=') value = end+1;
        if( (*end==';') || (*end==',') || (*end==0) )
        {
            char *nName = normalizedCookieParamName(name);
            char *nValue = value?normalizedCookieParamValue(value):0;
            if(!nName || !value)
            {
                if(nName) free(nName);
                if(nValue) free(nValue);
                if(*end==0) break;
                ++end;
                name = end;
                value = 0;
                continue;
            }

            if(nName[0]=='$')
            {
                Cookie *c;
                if(isDefault) c = &defaultCookie;
                else c = &current;

                if(!strcasecmp(nName,"$Version"))
                {
                    c->version = nValue;
                }
                else if(!strcasecmp(nName,"$Domain"))
                {
                    c->domain = nValue;
                }
                else if(!strcasecmp(nName,"$Path"))
                {
                    c->path = nValue;
                }
                else
                {
                    free(nValue);
                }
                free(nName);
            }
            else
            {
                if(isDefault)
                {
                    isDefault = 0;
                }
                else
                {
                    putCookie(cgi, &current, &defaultCookie);
                }
                current.name = nName;
                current.value = nValue;
            }

            if(*end==0) break;
            name = end+1;
            value = 0;
        }
        ++end;
    }

    if(current.name) putCookie(cgi, &current, &defaultCookie);

    return 1;
}

int cgiSetCookieParams(Cookie *cookie, char *version, char *value, char *domain, char *path, char *comment, int secure, int httpOnly, char *maxAge, char *expires)
{
    if(cookie->flushed) return 0;

    char *newVersion = 0;
    if(version)
    {
        newVersion = normalizedCookieParamValue(version);
        if(!newVersion) return 0;
    }

    char *newValue = 0;
    if(value)
    {
        newValue = normalizedCookieParamValue(value);
        if(!newValue) goto ERR;
    }

    char *newDomain = 0;
    if(domain)
    {
        newDomain = normalizedCookieParamValue(domain);
        if(!newDomain) goto ERR;
    }

    char *newMaxAge = 0;
    if(maxAge)
    {
        newMaxAge = normalizedCookieParamValue(maxAge);
        if(!newMaxAge) goto ERR;
    }

    char *newExpires = 0;
    if(expires)
    {
        newExpires = normalizedCookieParamValue(expires);
        if(!newExpires) goto ERR;
    }

    char *newPath = 0;
    if(path)
    {
        newPath = normalizedCookieParamValue(path);
        if(!newPath) goto ERR;
    }

    char *newComment = 0;
    if(comment)
    {
        newComment = normalizedCookieParamValue(comment);
        if(!newComment) goto ERR;
    }


    if(newVersion)
    {
        if(cookie->version) free(cookie->version);
        cookie->version = newVersion;
    }

    if(newValue)
    {
        if(cookie->value) free(cookie->value);
        cookie->value = newValue;
    }

    if(newDomain)
    {
        if(cookie->domain) free(cookie->domain);
        cookie->domain = newDomain;
    }

    if(newMaxAge)
    {
        if(cookie->maxAge) free(cookie->maxAge);
        cookie->maxAge = newMaxAge;
    }

    if(newExpires)
    {
        if(cookie->expires) free(cookie->expires);
        cookie->expires = newExpires;
    }

    if(newPath)
    {
        if(cookie->path) free(cookie->path);
        cookie->path = newPath;
    }

    if(newComment)
    {
        if(cookie->comment) free(cookie->comment);
        cookie->comment = newComment;
    }

    if(secure)
    {
        cookie->secure = secure;
    }

    if(httpOnly)
    {
        cookie->httpOnly = httpOnly;
    }

    return 1;

ERR:
    if(newVersion) free(newVersion);
    if(newValue) free(newValue);
    if(newDomain) free(newDomain);
    if(newMaxAge) free(newMaxAge);
    if(newExpires) free(newExpires);
    if(newPath) free(newPath);
    if(newComment) free(newComment);

    return 0;
}

Cookie *cgiSetCookie(CGI *cgi, char *name, char *version, char *value, char *domain, char *path, char *comment, int secure, int httpOnly, char *maxAge, char *expires)
{
    if(cgi->flushed) return 0;

    if(!cgi->cookies)
    {
        if(!cgiLoadCookies(cgi)) return 0;
    }

    return setCookie(cgi, name, version, value, domain, path, comment, secure, httpOnly, maxAge, expires);
}

void clearCookie(Cookie *cookie)
{
    if(cookie->value)
    {
        free(cookie->value);
        cookie->value = 0;
    }
}

int cgiClearCookieByNameVersion(CGI *cgi, char *name, char *version)
{
    if(cgi->flushed) return 0;

    if(!cgi->cookies)
    {
        if(!cgiLoadCookies(cgi)) return 0;
    }

    int i;
    for(i=0; i<cgi->nCookies; i++)
    {
        Cookie *cookie = &cgi->cookies[i];

        if(!strcmp(cookie->version,version) && !strcmp(cookie->name,name))
        {
            clearCookie(cookie);
            return 1;
        }
    }

    return 1;
}

int cgiClearCookie(Cookie *cookie)
{
    if(cookie->flushed) return 0;

    clearCookie(cookie);
    return 1;
}


Cookie *cgiCookie(CGI *cgi, char *name, char *version)
{
    if(!cgi->cookies)
    {
        if(!cgiLoadCookies(cgi)) return 0;
    }

    if(version==0) version = "0";

    int i;
    for(i=0; i<cgi->nCookies; i++)
    {
        Cookie *cookie = &cgi->cookies[i];

        if(!cookie->value) continue;

        if(!strcmp(cookie->version,version) && !strcmp(cookie->name,name))
        {
            return cookie;
        }
    }

    return 0;
}

void freeVars(CGIVars *cgiVars)
{
    int i;

    if(cgiVars->varsBuf) free(cgiVars->varsBuf);
    if(cgiVars->vars)
    {
        for(i=0; i<cgiVars->nvars; i++)
        {
            if(cgiVars->vars[i].values)
                free(cgiVars->vars[i].values);
        }
        free(cgiVars->vars);
    }
    free(cgiVars);
}

void cgiDelete(CGI *cgi)
{
    int i;

    if(cgi->cgiout) fclose(cgi->cgiout);

    if(cgi->headerVars)
    {
        for(i=0; i<cgi->nHeaderVars; i++)
        {
            free(cgi->headerVars[i].name);
            if(cgi->headerVars[i].value) free(cgi->headerVars[i].value);
        }
        free(cgi->headerVars);
    }

    if(cgi->postVars) freeVars(cgi->postVars);
    if(cgi->getVars) freeVars(cgi->getVars);

    if(cgi->cookies)
    {
        for(i=0; i<cgi->nCookies; i++)
        {
            Cookie *cookie = &cgi->cookies[i];
            free(cookie->name);
            free(cookie->version);
            free(cookie->value);
            free(cookie->comment);
            free(cookie->domain);
            free(cookie->maxAge);
            free(cookie->expires);
            free(cookie->path);
        }
        free(cgi->cookies);
    }

    free(cgi);
}

CGI *cgiInit()
{
    CGI *cgi = (CGI *)malloc(sizeof(CGI));
    if(!cgi) return 0;

    cgi->cgiout = tmpfile();
    if(!cgi->cgiout)
    {
        free(cgi);
        return 0;
    }

    cgi->flushed = 0;

    cgi->headerVars = 0;
    cgi->nHeaderVars = 0;
    cgi->maxHeaderVars = 0;

    cgi->postVars = 0;
    cgi->getVars = 0;

    cgi->cookies = 0;
    cgi->maxCookies = 0;
    cgi->nCookies = 0;

    return cgi;
}

//buffer deve possuir no minimo, um tamanho onde caiba o boundary (+/- 80 bytes). 
int readStdinDataOrLine(char *buf, int bufLen)
{
    if(feof(stdin)) return 0;

    int len = 0;
    while(len<bufLen)
    {
        char b;
        if(fread(&b,1,1,stdin)<1) break;
        buf[len] = b;
        ++len;
        if(b=='\n') break;
    }

    return len;
}


char *readStdinLine()
{
    char buf[1024];
    if(feof(stdin)) return 0;

    int lineLen = 0;
    int maxLen = 1024;
    char *line = (char *) malloc(sizeof(char)*maxLen);
    if(!line) return 0;
    line[0] = 0;

    while(1)
    {
        if(!fgets(buf,1024,stdin)) break;
        int len = strlen(buf);
        if( (len+lineLen+1) >= maxLen )
        {
            char *newLine = (char *)realloc(line,sizeof(char)*(maxLen+1024));
            if(!newLine) goto ERR;
            line = newLine;
        }
        strcat(line,buf);
        lineLen+=len;
        if(line[lineLen-1]=='\n') break;
    }

    return line;

ERR:
    free(line);
    return 0;
}

int readMultipartHeader(char **contentDisposition, char **contentType, char **contentTransferEncoding)
{
    while(1)
    {
        char *line = readStdinLine();
        if(!line) return 0;

        if(myStrCaseStr(line,"content-type: ")) *contentType = line;
        else if(myStrCaseStr(line,"content-disposition: ")) *contentDisposition = line;
        else if(myStrCaseStr(line,"content-transfer-encoding: ")) *contentTransferEncoding = line;
        else if(!strcmp(line,"\r\n") || !strcmp(line,"\n")) {free(line); break;}
        else free(line);
    }

    return 1;
}

#define START_BOUNDARY 1
#define END_BOUNDARY 2
int isBoundary(char *boundary, char *line, int lineLen)
{
    if( (lineLen<3) && (line[lineLen-1]!='\n') ) return 0;

    if( (line[0]!='-') || (line[1]!='-') ) return 0;

    line+=2;

    int i;

    for(i=0; i<lineLen; i++)
    {
        if( (line[i]=='\r') || (line[i]=='\n') )
        {
            line[i] = 0;
            break;
        }
    }

    int boundaryLen = strlen(boundary);
    if( !strncmp(boundary,line,boundaryLen) )
    {
        if(!line[boundaryLen]) return START_BOUNDARY;
        if( (line[boundaryLen]=='-') && (line[boundaryLen+1]=='-') ) return END_BOUNDARY;
    }

    return 0;
}

int catVars(char **vars, char *data)
{
    char *oldBuf = vars?*vars:0;

    int len = 0;
    if(oldBuf) len = strlen(oldBuf);

    
    int dataLen;
    for(dataLen=0; data[dataLen]; ++dataLen)
    {
        if( (data[dataLen]=='\r') || (data[dataLen]=='\n') )
        {
printf("DATA LEN %d = %d\n",dataLen,data[dataLen]);
            data[dataLen] = 0;
            break;
        }
    }
printf("DATA LEN %d\n",dataLen);

    char *newBuf = (char *)realloc(oldBuf,sizeof(char)*(len+dataLen+1));
    if(!newBuf) return 0;
    newBuf[len] = 0;

    strcat(newBuf,data);
    *vars = newBuf;

    return 1;
}

int putFile(CGIFileVar *fileVar, char *fileName, char *path, char *contentType)
{
    char *lFileName = (char *)malloc(sizeof(char)*(strlen(fileName)+1));
    if(!lFileName) return 0;

    char *lPath = (char *)malloc(sizeof(char)*(strlen(path)+1));
    if(!lPath) goto ERR;

    char *lContentType = 0;
    if(contentType)
    {
        if(contentType = myStrCaseStr(contentType,"content-type: "))
        {
            contentType += 14;
            lContentType = strdup(contentType);
            if(!lContentType) goto ERR;
        }
    }

    CGIFile *newFiles = (CGIFile *)realloc(fileVar->files, sizeof(CGIFile)*(fileVar->nFiles+1));
    if(!newFiles) goto ERR;

    fileVar->files = newFiles;

    fileVar->files[fileVar->nFiles].name = lFileName;
    fileVar->files[fileVar->nFiles].path = lPath;
    fileVar->files[fileVar->nFiles].contentType = lContentType;

    fileVar->nFiles++;

    return 1;

ERR:
    free(lFileName);
    if(lPath) free(lPath);
    if(lContentType) free(lContentType);

    return 0;
}

int addFile(CGI *cgi, char *varName, char *fileName, char *path, char *contentType)
{
    int i;

    for(i=0; i<cgi->nFileVars; i++)
    {
        if(!strcmp(varName,cgi->fileVars[i].name))
        {
            return putFile(&cgi->fileVars[i],fileName,path,contentType);
        }
    }

    if( cgi->nFileVars==cgi->maxFileVars )
    {
        CGIFileVar *newFileVars = (CGIFileVar *)realloc(cgi->fileVars,sizeof(CGIFileVar)*(cgi->maxFileVars+10));
        if(!newFileVars) return 0;
        cgi->maxFileVars += 10;
        cgi->fileVars = newFileVars;
    }

    CGIFileVar *fileVar = &cgi->fileVars[cgi->nFileVars];
    memset(fileVar, 0, sizeof(CGIFileVar));

    char *name = (char *)malloc(sizeof(char)*(strlen(varName)+1));
    if(!name) return 0;

    if(!putFile(fileVar,fileName,path,contentType)) goto ERR;
    fileVar->name = name;

    cgi->nFileVars++;

    return 1;

ERR:
    free(name);
    return 0;
}

int readMultipartFile(CGI *cgi, char *boundary, char *varName, char *fileName, char *contentType, char *contentTransferEncoding)
{
    int result;
    char tmpBuf[1024];

printf("Loading file %s %s %s\n",varName,fileName,contentType);
    char *ftmp = (char *)malloc(sizeof(char)*L_tmpnam);
    if(!ftmp) return 0;

    if(!tmpnam(ftmp)) goto ERR;

    FILE *fd = fopen(ftmp,"wb+");
    if(!fd) goto ERR;

    while(result = readStdinDataOrLine(tmpBuf,1024))
    {
        int btype = isBoundary(boundary,tmpBuf,result);
        if(btype)
        {
            result = btype;
            break;
        }
        fwrite(tmpBuf, 1, result, fd);
    }

    fclose(fd);
printf("Loaded %s\n",ftmp);

    if(!result) goto ERR;

    if(!addFile(cgi, varName, fileName, ftmp, contentType)) goto ERR;

    return result;

ERR:
    free(ftmp);

    return 0;
}

int readMultipartData(CGI *cgi, char *boundary, char **vars, char *mixedVarName, char *contentDisposition, char *contentType, char *contentTransferEncoding)
{
    int i, result = 0;
    char tmpBuf[1024];

    char *varBuf = 0;
    int bufSz = 0;
    int len = 0;

    if(myStrCaseStr(contentDisposition,"content-disposition: file;"))
    {
        char *name = mixedVarName;
        char *fileName = myStrCaseStr(contentDisposition, "; filename=\"");

printf("content disposition FILE %s %s\n",name,fileName);
        if(name && fileName)
        {
            fileName+=12;
            for(i=0; fileName[i]; i++)
            {
                if(fileName[i]=='"')
                {
                    fileName[i] = 0;
                    break;
                }
            }

            result = readMultipartFile(cgi, boundary, name, fileName, contentType, contentTransferEncoding);
        }
        if(!result) goto ERR;
    }
    else if(myStrCaseStr(contentDisposition,"content-disposition: form-data;"))
    {
printf("content disposition FORM DATA\n");
        char *name = myStrCaseStr(contentDisposition, "; name=\"");
        if(!name) goto ERR;

printf("content disposition NAME %s\n",name);
        char *fileName = myStrCaseStr(contentDisposition, "; filename=\"");

        name+=8;
        for(i=0; name[i]; i++)
        {
            if(name[i]=='"')
            {
                name[i] = 0;
                break;
            }
        }

        if(fileName)
        {
            fileName+=12;
            for(i=0; fileName[i]; i++)
            {
                if(fileName[i]=='"')
                {
                    fileName[i] = 0;
                    break;
                }
            }
printf("content disposition FILENAME %s\n",fileName);

            result = readMultipartFile(cgi, boundary, name, fileName, contentType, contentTransferEncoding);
            if(!result) goto ERR;
        }
        else
        {
printf("content disposition VAR\n");
            int nameLen = strlen(name);
            if( (len+nameLen+3) >= bufSz )
            {
                char *newBuf = (char *)realloc(varBuf,sizeof(char)*(len+nameLen+3+1024));
                if(!newBuf) goto ERR;
                varBuf = newBuf;
                bufSz = len+nameLen+3+1024;
            }

            if(*vars)
            {
                memcpy(&varBuf[len],"&",sizeof(char));
                ++len;
            }

            memcpy(&varBuf[len],name,sizeof(char)*nameLen);
            len+=nameLen;

            memcpy(&varBuf[len],"=",sizeof(char));
            len++;

            while(result = readStdinDataOrLine(tmpBuf,1024))
            {
                int btype = isBoundary(boundary,tmpBuf,result);
                if(btype)
                {
                    result = btype;
                    break;
                }

                if( (len+result+1) >= bufSz )
                {
                    char *newBuf = (char *)realloc(varBuf,sizeof(char)*(len+result+1+1024));
                    if(!newBuf) goto ERR;
                    varBuf = newBuf;
                    bufSz = len+result+1+1024;
                }

                memcpy(&varBuf[len],tmpBuf,result);
                len+=result;
                varBuf[len] = 0;
            }
            if(!result) goto ERR;

            if(varBuf)
            {
                if(!catVars(vars, varBuf)) goto ERR;
                free(varBuf);
            }
printf("VARS %s\n",*vars);
        }
    }

    return result;

ERR:
    if(varBuf) free(varBuf);

    return 0;
}

int findBoundary(char *boundary)
{
    char buf[1024];

    while(1)
    {
        int result = readStdinDataOrLine(buf, 1024);

        if(!result) return 0;

        int btype = isBoundary(boundary,buf,result);
        if(btype) return btype;
    }

    return 0;
}

int loadMultipart(CGI *cgi, char *boundary, char **vars, char *mixedVarName)
{
    int result;
    char *contentDisposition=0;
    char *contentType=0;
    char *contentTransferEncoding=0;

printf("Finding %s\n",boundary);
    result = findBoundary(boundary);
printf("Load Multipart %d\n",result);

    while(result == START_BOUNDARY)
    {
        if(contentDisposition) {free(contentDisposition); contentDisposition=0;}
        if(contentType) {free(contentType); contentType=0;}
        if(contentTransferEncoding) {free(contentTransferEncoding); contentTransferEncoding=0;}

        result = readMultipartHeader(&contentDisposition, &contentType, &contentTransferEncoding);
        if(!result || !contentDisposition)
        {
            result = 0;
            break;
        }

        int isMultipart = 0;
        if(contentType)
        {
            if(myStrCaseStr(contentType,"content-type: multipart/mixed;")) isMultipart = 1;
        }

        if(isMultipart)
        {
            int i;
            char *newBoundary = myStrCaseStr(contentType,"; boundary=");
            char *mixedVarName = myStrCaseStr(contentDisposition,"; name=\"");

            if(newBoundary && mixedVarName)
            {
                newBoundary += 11;
                for(i=0; newBoundary[i]; i++)
                {
                    if( (newBoundary[i]=='\r') || (newBoundary[i]=='\n') )
                    {
                        newBoundary[i] = 0;
                        break;
                    }
                }

                mixedVarName += 8;
                for(i=0; mixedVarName[i]; i++)
                {
                    if( mixedVarName[i]=='"' )
                    {
                        mixedVarName[i] = 0;
                        break;
                    }
                }

                result = loadMultipart(cgi, newBoundary, vars, mixedVarName);
                if(!result) break;
                result = findBoundary(boundary);
            }
            else
            {
                result = 0;
                break;
            }
        }
        else
        {
            result = readMultipartData(cgi,boundary,vars,mixedVarName,contentDisposition,contentType,contentTransferEncoding);
            if(!result) break;
        }
    }
printf("Multipart End %d\n",result);

    if(contentDisposition) free(contentDisposition);
    if(contentType) free(contentType);
    if(contentTransferEncoding) free(contentTransferEncoding);

    return result;
}
